package erfgame.core.world.terrain;

import java.util.logging.Logger;

import erfgame.core.Point3D;

public final class HorizontalAngleVoxelColorPicker extends AbstractVoxelColorPicker {
	
	private static final Logger log = Logger.getLogger( HorizontalAngleVoxelColorPicker.class.getName() );
	
	private int[][] verticalAtanValues;
	private int[][] horizontalAtanValues;
	
	private double horizontalLightAngle;
	private double verticalLightAngle;
	private int verticalLightSin;
	
	private TerrainColorSource unknownColorSource;
	
	private Point3D xzresult;
	private Point3D yzresult;
	private Point3D xyresult;
	
	public HorizontalAngleVoxelColorPicker( double horizontalLightAngle, double verticalLightAngle, int smoothness, TerrainColorSource unknownColorSource, int blockWidth, int blockHeight, int blockDepth, boolean useLog ) {
		super( smoothness, blockWidth, blockHeight, blockDepth, useLog );
		this.verticalAtanValues = generateAtanValues( verticalLightAngle, smoothness, false );
		this.horizontalAtanValues = generateAtanValues( horizontalLightAngle, smoothness, false );
		this.unknownColorSource = unknownColorSource;
		this.horizontalLightAngle = horizontalLightAngle;
		this.verticalLightAngle = verticalLightAngle;
		this.verticalLightSin = (int)(ONE * Math.sin( verticalLightAngle + Math.PI/2 ));
		
		this.xzresult = new Point3D();
		this.yzresult = new Point3D();
		this.xyresult = new Point3D();
	}
	
	public final int atan2sinVertical( int a, int b ) {
		return verticalAtanValues[b + smoothnessTimes2][a + smoothnessTimes2];
	}
	
	public final int atan2sinHorizontal( int a, int b ) {
		return horizontalAtanValues[b + smoothnessTimes2][a + smoothnessTimes2];
	}
	
	
	
	@Override
	public int getColor(TerrainColorSource colorSource, int cx, int cy, int cz,
			byte[][][][][][] terrain, int tx, int ty, int tz, int dCeiling,
			int worldX, int worldY, int worldZ, boolean clearRoof) {
		// TODO Auto-generated method stub
		return 0;
	}

	public final int getMultiplier( 
			int cx, 
			int cy, 
			int cz,
			byte[][][][][][] terrain, 
			int tx, 
			int ty, 
			int tz, 
			int worldX, 
			int worldY, 
			int worldZ,
			boolean clearRoof
	) {
		Point3D xzresult = this.xzresult;
		Point3D yzresult = this.yzresult;
		Point3D xyresult = this.xyresult;
		
		// TODO use internal values
		VerticalEdge xzturn = new VerticalEdge();
		VerticalEdge yzturn = new VerticalEdge();
		
		boolean xzValid = getPathDeltas(
				cx, 
				cy, 
				cz, 
				terrain, 
				tx, 
				ty, 
				tz, 
				XZ_LEFT_HANDED, 
				XZ_RIGHT_HANDED, 
				false,
				xzresult,
				xzturn,
				clearRoof
		);

		boolean yzValid = getPathDeltas(
				cx, 
				cy, 
				cz, 
				terrain, 
				tx, 
				ty, 
				tz, 
				YZ_LEFT_HANDED, 
				YZ_RIGHT_HANDED, 
				false,
				yzresult,
				yzturn,
				clearRoof
		);
		
		int mult;

		int xycx;
		int xycy;
		int xycz;
		int xytx;
		int xyty;
		int xytz;
		int xyremainingLength;
		if( (xzturn.remainingLength != 0 || yzturn.remainingLength != 0) ) {
			if( xzturn.remainingLength > yzturn.remainingLength ) {
				xytx = xzturn.x;
				xyty = xzturn.y;
				xytz = xzturn.z;
				xycx = xzturn.cx;
				xycy = xzturn.cy;
				xycz = xzturn.cz;
				xyremainingLength = xzturn.remainingLength;
			} else {
				xytx = yzturn.x;
				xyty = yzturn.y;
				xytz = yzturn.z;
				xycx = yzturn.cx;
				xycy = yzturn.cy;
				xycz = yzturn.cz;
				xyremainingLength = yzturn.remainingLength;
			}
		} else {
			xytx = tx;
			xyty = ty;
			xytz = tz;
			xycx = cx;
			xycy = cy;
			xycz = cz;
			xyremainingLength = smoothnessMinus1;
		}
		
		boolean xyValid;
		
		if( smoothnessMinus1 - xyremainingLength < 4 ) {
			xyValid = getPathDeltas(
					xycx, 
					xycy, 
					xycz, 
					terrain, 
					xytx, 
					xyty, 
					xytz, 
					XY_LEFT_HANDED, 
					XY_RIGHT_HANDED, 
					false,
					xyresult,
					null,
					clearRoof
			);	
		} else {
			// too far away
			xyValid = false;
		}
		
//    	if( xyValid && xzValid && yzValid ) {
//			// pick best two 
//			if( yzresult.y == 0 ) {
//				// the light angle is not flat, so use the other values
//				yzValid = false;
//			} else {
//				int xyQuality = Math.abs(xyresult.x);
//				int xzQuality = Math.abs(xzresult.x);
//				if( xyQuality > xzQuality ) {
//	                xzValid = false;
//				} else {
//					xyValid = false;
//				}				
//		}
//		}
			
		if( yzValid || xzValid || xyValid ) {
//			int x1 = xzresult.x * yzresult.z;
//			int y2 = yzresult.y * xzresult.z;

			
//			double horizontalAngle = Math.atan2(-(y2 * z1 / z2), x1);
			// TODO I think this returns the light angle incorrectly by 90 degrees
			double horizontalAngle;
			double horizontalSin;
			if(xyValid ) {
				horizontalAngle = Math.atan2(xyresult.y, xyresult.x);
				horizontalSin = Math.sin( horizontalAngle + horizontalLightAngle );
			} else {
				// ridiculously inaccurate
				horizontalAngle = Math.atan2(
						yzresult.y*xzresult.z, 
						xzresult.x*yzresult.z) + Math.PI/2;								
				horizontalSin = Math.sin( horizontalAngle + horizontalLightAngle );
			}
//			double angle = Math.atan2(
//					((double)yzresult.y) / Math.abs((double)yzresult.z), 
//					((double)xzresult.x) / Math.abs((double)xzresult.z)
//			);
			if( xzresult.x != 0 && yzresult.y != 0 ) {
				double x1 = xzresult.x;
				double y2 = yzresult.y;
				double z1 = xzresult.z;
				double z2 = yzresult.z;
//				horizontalAngle = Math.atan2((y2 * z1)/z2, x1);								
//				double xi = y2 / ( Math.tan(horizontalLightAngle) + y2/x1);
				// can tan be replaced with m?
				double xi = y2 / ( Math.tan(horizontalAngle + Math.PI/2) + y2/x1);
//				double yi = y2 * (xi - x1) / -x1; -- seems to produce same result
				double yi = (y2/-x1) * xi + y2;
				double zi = z1 + (xi - x1) * (z2 - z1) / -x1;
				// TODO could be quicker to work this out with sin(horizontalLightAngle)*xi
				double hi = Math.sqrt( xi*xi + yi*yi );
				double verticalAngle = Math.atan2(zi, hi);
				double verticalSin = Math.sin(verticalAngle + verticalLightAngle);
				
				mult = (int)(ONE * Math.abs( verticalSin ) * horizontalSin );
//				mult = (int)(ONE * verticalSin);
			} else if( xzresult.x != 0 ) {
				// assume xy is valid (should be)
				int verticalSin = atan2sinVertical(xzresult.z, xzresult.x);
//				double verticalAngle = Math.atan2(z1, x1);
//				double verticalSin = Math.sin(verticalAngle + verticalLightAngle);
				mult = (int)(Math.abs(verticalSin) * horizontalSin);			
			} else if( yzresult.y != 0 ) {
				int verticalSin = atan2sinVertical(yzresult.z, yzresult.y);
//				double verticalAngle = Math.atan2(z2, y2);
//				double verticalSin = Math.sin(verticalAngle + verticalLightAngle);
				mult = (int)(Math.abs(verticalSin) * horizontalSin);
			} else {
				// TODO : if y2 != 0 there are things we can do
				// TODO pregenerate this value
//				double verticalSin = Math.sin(verticalLightAngle + Math.PI/2);
				mult = (int)(verticalLightSin * horizontalSin);
			}
			
			//int xy = atan2sinHorizontal(yzresult.y, xzresult.x);
			//mult = xy;
		} else {
			//return this.unknownColorSource.getColor(worldX, worldY, worldZ, 0, 0, dCeiling);
			return INVALID;
		}
		
//		int color = colorSource.getColor( 
//				worldX, 
//				worldY, 
//				worldZ, 
//				mult, 
//				PRECISION, 
//				dCeiling 
//		);
//				
//		return color;
		return mult;
	}
	
}
